package figure8;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.sound.sampled.Port;

import figure8.Network.Client;
import figure8.Network.Message;
import figure8.Network.Message.Type;
import figure8.events.TrackAddedEvent;
import figure8.events.TrackEventListener;
import figure8.events.TrackRemovedEvent;

public class Project {
	private LoopTiming timing;
	private List<Track> tracks;
	private String name;
	private File location;
	

	private File directory;

	protected List<TrackEventListener> listeners;
	
	public Map<Integer, Track> trackIDs;
	
	public static class InvalidProjectException extends Exception {
		public InvalidProjectException(String message) {
			super(message);
		}
	}

	// Constructor for creating a new project with a given timing
	public Project(LoopTiming timing, String name, File projectDir) {
		this.timing = timing;
		this.name = name;
		this.tracks = new LinkedList<Track>();
		this.trackIDs = new HashMap<Integer, Track>();
		this.listeners = new ArrayList<TrackEventListener>();
		
		File dir = null;
		// Create directory for the project
		if(projectDir != null)
		{
			dir = new File(projectDir + File.separator + this.name);
			if(dir.exists() == false) {
				dir.mkdir();
			}
			this.location = dir;

			File recordingFile = new File(dir.getAbsolutePath() + File.separator + "Recordings");
			recordingFile.mkdir();
			
			//Trying saving object directly to a file
			try
			{
				File timingFile = new File(this.location.getAbsolutePath() + File.separator 
						+ "timing.f8p");

				ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(timingFile));
				stream.writeObject(this.timing);
				stream.close();
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	
	public void play() {
		for (Track track : this.tracks) {
			track.play();
		}
	}
	
	public void playDrumTrack () {
		if (tracks.get(0).isDrum())
			((DrumMachineTrack) tracks.get(0)).playBack();
	}
	
	public void countIn () {
		if (tracks.get(0).isDrum())
			((DrumMachineTrack) tracks.get(0)).countIn();
	}
	
	public void pause() {
		for (Track track : this.tracks) {
			track.pause();
		}
	}
	
	public void stop() {
		for (Track track : this.tracks) {
			track.stop();
		}
	}
	
	public void addTrack(Track track) {
		this.addTrack(track, null);
	}
	
	public void addTrack(Track track, Client client) {
		this.tracks.add(track);
		this.trackIDs.put(track.getID(), track);
		this.fireTrackAddedEvent(track, client);
		
		System.out.println(track.getName() + " (" + track.getID() + ") is now in the project");
	}
	
	public DrumMachineTrack getDrums() {
		for (Track track : this.tracks) {
			if(track.isDrum()) return (DrumMachineTrack) track;
		}
		return null;
	}
	
	public void deleteTrack (Track track, Client client) {
		/*int i = 0;
		for (Track tracky : this.tracks) {
			if (tracky.getName().equals(track.getName())) {
				this.fireTrackRemovedEvent(this.tracks.get(i));
				this.tracks.remove(i);
			}
			i++;
		}*/
		
		System.out.println("REMOVING TRACK " + track.getID());
		
		new Throwable().getStackTrace();
		
		this.tracks.remove(track);
		//this.trackIDs.remove(track.getID());
		this.fireTrackRemovedEvent(track, client);
	}
	
	public Track getTrack(int id) {
		return this.trackIDs.get(id);
	}
	
	// Events listener stuff
	public void addTrackEventListener(TrackEventListener listener) {
		this.listeners.add(listener);
	}

	public void removeTrackEventListener(TrackEventListener listener) {
		this.listeners.remove(listener);
	}

	protected void fireTrackAddedEvent(Track track) {
		for (TrackEventListener listener : this.listeners) {
			listener.trackAdded(new TrackAddedEvent(this, track, null));
		}
	}
	
	protected void fireTrackAddedEvent(Track track, Client client) {
		for (TrackEventListener listener : this.listeners) {
			listener.trackAdded(new TrackAddedEvent(this, track, client));
		}
	}
	
	protected void fireTrackRemovedEvent(Track track, Client client) {
		for (TrackEventListener listener : this.listeners) {
			listener.trackRemoved(new TrackRemovedEvent(this, track, client));
		}
	}
	//
	
	public void updateTimings (LoopTiming timing) {
		this.timing = timing;
	}
	
	
	// Constructor for loading a project
	public Project(File directory, Controller controller) throws InvalidProjectException {
		this.directory = directory;
		this.trackIDs = new HashMap<Integer, Track>();
		// Load the data from a file
		ObjectInputStream stream = null;
		try {
			stream = new ObjectInputStream(new FileInputStream(new File(
					directory + File.separator + "timing.f8p")));
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		try {
			this.timing = (LoopTiming) stream.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		try {
			stream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.name = directory.getName();
		this.location = new File(directory.getAbsolutePath());
		this.tracks = new LinkedList<Track>();
		this.listeners = new ArrayList<TrackEventListener>();
	}

	public void closeLines() {
		for (Track track : this.tracks) {
			track.close();
		}
	}
	
	public void soloTrack(Track track) {
		Boolean soloing;		

		soloing = track.getSoloing();
		
		for(Track trk : tracks)
			if(trk != track)
				trk.setMuted(soloing);
		/*for(Track trk : midiTrack)
			if(trk != track)
				trk.setMuted(soloing);
		for(Track trk : drumMachineTrack)
			if(trk != track)
				trk.setMuted(soloing);*/
	}
	
	// Save the project
	public void save() {
		//Create text file containing LoopTimings
		/*File projectFile = new File(this.location.getAbsolutePath() + File.separator + "project.f8p");
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter(projectFile));
			String output = Integer.toString(this.getTiming().getTempo());
			output = output + "," + Integer.toString(this.getTiming().getBars());
			output = output + "," + Integer.toString(this.getTiming().getBeats());
			output = output + "," + Integer.toString(this.getTiming().getNote());
			out.write(output);
			out.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}*/

		// Save the loop timing object to a file
		try
		{
			File timingFile = new File(this.location.getAbsolutePath() + File.separator + "timing" + ".f8p");
			ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(timingFile));
			stream.writeObject(this.timing);
			stream.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	// Render the project to a single audio file
	public void export() {
		System.err.println("Exporting a project is not implemented!");
	}

	public LoopTiming getTiming() {
		return this.timing;
	}

	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public List<Track> getTracks() {
		return tracks;
	}

	public String toString() {
		StringBuilder output = new StringBuilder();

		output.append("Project:\n");
		output.append("  Details:\n");
		output.append(this.timing);
		output.append("  Tracks:\n");

		if (this.tracks.isEmpty()) {
			output.append("    None");
		} else {
			for (Track track : this.tracks) {
				output.append("    ");
				output.append(track);
				output.append('\n');
			}
		}

		return output.toString();
	}

	public File getLocation() {
		return location;
	}
	
	public void send(Client client) {
		String name = this.name.replace(' ', '_');
		String timing = this.timing.getTempo() + " " + this.timing.getBars() + " " + this.timing.getBeats() + " " + this.timing.getNote();
		
		Message message = new Message(Type.PROJECT_NEW, name + " " + timing);
		
		client.send(message);
		
		for (Track track : this.getTracks()) {
			track.send(client, null);
		}
	}

	public void fromString(String data, Client client) {
		String parts[] = data.split(" ");
		
		String name = parts[0].replace('_', ' ');
		LoopTiming timing = new LoopTiming(Integer.parseInt(parts[1]),Integer.parseInt(parts[2]),Integer.parseInt(parts[3]),Integer.parseInt(parts[4]));
		
		this.setName(name);
		this.updateTimings(timing);
		
		for (Track track : new ArrayList<Track>(this.tracks)) {
			this.deleteTrack(track, client);
		}
	}

	private Object ArrayList(List<Track> tracks2) {
		// TODO Auto-generated method stub
		return null;
	}
}
